/*
 *  Created at 2011-01-20 00:20 GMT-03:00
 *  Author: Victor Williams Stafusa da Silva
 *  JSmartUnit
 */
package org.jsmartunit.model;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;

/**
 * @author Victor Williams Stafusa da Silva
 */
public final class ClassStructure {

    private static final Map<Class<?>, ClassStructure> knownStructures = new HashMap<Class<?>, ClassStructure>(47);

    private final List<Method> beforeMethods;
    private final List<Method> afterMethods;
    private final List<Method> beforeClassMethods;
    private final List<Method> afterClassMethods;

    private final List<Field> ruleFields;
    private final List<Field> classRuleFields;

    public static synchronized ClassStructure getStructure(Class<?> targetClass) {
        ClassStructure c = knownStructures.get(targetClass);
        if (c == null) {
            c = new ClassStructure(targetClass);
            knownStructures.put(targetClass, c);
        }
        return c;
    }

    private ClassStructure(Class<?> targetClass) {
        this.beforeMethods = new LinkedList<Method>();
        this.afterMethods = new LinkedList<Method>();
        this.beforeClassMethods = new LinkedList<Method>();
        this.afterClassMethods = new LinkedList<Method>();
        this.ruleFields = new LinkedList<Field>();
        this.classRuleFields = new LinkedList<Field>();

        for (Method m : targetClass.getMethods()) {
            if (m.isAnnotationPresent(Before.class)) beforeMethods.add(0, m);
            if (m.isAnnotationPresent(After.class)) afterMethods.add(m);
            if (m.isAnnotationPresent(BeforeClass.class)) beforeClassMethods.add(0, m);
            if (m.isAnnotationPresent(AfterClass.class)) afterClassMethods.add(m);
        }

        for (Field f : targetClass.getFields()) {
            if (f.isAnnotationPresent(Rule.class)) ruleFields.add(f);
            if (f.isAnnotationPresent(ClassRule.class)) classRuleFields.add(f);
        }
    }

    public List<Method> getBeforeMethods() {
        return Collections.unmodifiableList(beforeMethods);
    }

    public List<Method> getAfterMethods() {
        return Collections.unmodifiableList(afterMethods);
    }

    public List<Method> getBeforeClassMethods() {
        return Collections.unmodifiableList(beforeClassMethods);
    }

    public List<Method> getAfterClassMethods() {
        return Collections.unmodifiableList(afterClassMethods);
    }

    public List<Field> getRuleFields() {
        return Collections.unmodifiableList(ruleFields);
    }

    public List<Field> getClassRuleFields() {
        return Collections.unmodifiableList(classRuleFields);
    }

    public static Object invokeExplosively(InstanceFactory<?> instanceFactory, Method method, Object[] args) throws Throwable {
        return invokeExplosively(instanceFactory.getInstance(), method, args);
    }

    public static Object invokeExplosively(Object instance, Method method, Object[] args) throws Throwable {
        try {
            return method.invoke(instance, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException(
                    "Instance type = " + (instance == null ? "null" : instance.getClass().getName())
                    + ", Method = " + method.toString()
                    + ", Declaring class = " + method.getDeclaringClass().getName()
                    + ", Arguments = " + Arrays.asList(args).toString(), e);
        }
    }

    public static <U> U invokeExplosively(Constructor<U> constructor, Object[] args) throws Throwable {
        try {
            return constructor.newInstance(args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException(
                    "Constructor = " + constructor.toString()
                    + ", Arguments = " + Arrays.asList(args).toString(), e);
        }
    }
}
